JS 줌 앤 팬(Zoom and Pan) 구현하기(1)

April 26, 2024

JavaScript

지난 프로젝트에서 개발했던 주요 기능중 하나는 웹 메거진을 보는 pdf viewer에 스크롤을 통한 확대 축소 및 드래그를 통한 탐색 기능이었다.

당시에는 시간에 쫓겨 라이브러리로 구현했지만 이번 기회에 줌 앤 팬 기능을 바닐라 자바스크립트로 직접 구현해볼것이다.


구현해야할 기능들

  • -wheel을 통한 scale 변경
  • -drag 기능
  • -bound 기능

정리하면 마위스 휠을 통해 scale을 조절하여 zoom-in, zoom-out 기능을 구현해야하고

확대가 된 상태에서 drag를 통해 viewer 영역 밖에 있는 컨텐츠를 탐색이 가능해야한다.

마지막으로 드래그시 컨텐츠의 모서리가 viewer 모서리보다 안으로 들어오면 안된다.


먼저 컨텐츠가 담겨있는 요소는 #target으로 사용자가 보는 영역은 #viewer로 아이디 값을 지정해 주었다.


이제 wheel을 통한 scale변경을 구현해보자.

viewer.addEventListener('wheel', function (e) {
        e.preventDefault();
        const rect = viewer.getBoundingClientRect();

        const mouseX = e.clientX - rect.left;
        const mouseY = e.clientY - rect.top;

        const deltaScale = Math.sign(e.deltaY) * -0.05;
        const newScale = Math.max(1, Math.min(3, scale + deltaScale));

        scale = newScale;
        target.style.transformOrigin = `${mouseX}px ${mouseY}px`;
        target.style.transform = `scale(${scale})`;
    });

처음에는 단순히 e.clientX, e.clientY로 좌표를 받고 그 좌표를 transformOrigin으로 설정하여 scale을 변경하고자 했다.
이렇게 하면 zoom-in, zoom-out는 잘 동작한다.

문제는 드래그로 target의 위치를 이동시킬 때 transformtranslate을 이용해서 이동시키는데 scale을 변경하고 첫 드래그 시 target의 위치정보가 없어서 점프하는 현상이 일어났다.


그래서 transform-origin을 직접 구현해줬다.



우선 viewer 중앙 우측 상단에 휠 이벤트를 발생시켜보자. (초록책 점)



transform-origin 속성의 기본 값은 50% 50%이기 때문에 중앙을 기준으로 확대된다.


우측 상단을 기준으로 확대되는것 처럼 보이게 하려면 target이 좌측 하단 화살표만큼 움직여줘야한다.



그럼 어떻게 얼마만큼 움직여야할까?


현재 scale변경은 viewer의 중앙점을 기준으로 이루어진다. 즉 중앙이 겹쳐있는 상태에서 x축을 기준으로 오른쪽으로 움직이면 +, 왼쪽으로 움직이면 -, y축은 아래로 움직이면 +, 위로 움직이면 -가 된다.


이미지 2번에서 3번으로 변하려면 target이 좌하단으로 이동해야한다.

우선 스크롤 이벤트가 발생환 좌표와 viewer의 중앙좌표 사이의 거리를 구하고 현재 scale - 1한 값을 곱한다.

여기서 newScale - 1은 새로운 스케일과 기본 스케일의 차이이다.
newScale이 1보다 클 때, -1을 하면 확대된 정도만큼만 계산하게 된다.


또한 다시 scale을 1로 돌려놓을 때 viewer 와 target이 일치하도록 하는 로직을 추가해준다.


    initialX = -(mouseX - ((rect.right - rect.left) / 2)) * (newScale - 1)
    initialY = -(mouseY - ((rect.bottom - rect.top) / 2)) * (newScale - 1)                

    if (newScale === 1) {
        initialX = 0;
        initialY = 0;
    }

    scale = newScale;
    
    target.style.transform = `translateX(${initialX}px) translateY(${initialY}px) scale(${scale})`;

완성!

글이 길어져서 다음편에서 Drag를 구현해보겠다



Profile picture

Written by yongho FrontEnd Developer